{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Description:\n",
    "这里用矩阵分解算法实现博客上的那个demon， 预测用户打分的那个"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-08-26T14:30:24.370192Z",
     "start_time": "2020-08-26T14:30:24.367199Z"
    }
   },
   "outputs": [],
   "source": [
    "import random\n",
    "import math"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 33,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-08-26T14:31:29.448498Z",
     "start_time": "2020-08-26T14:31:29.439522Z"
    }
   },
   "outputs": [],
   "source": [
    "class RSVD():\n",
    "    def __init__(self, rating_data, F=5, alpha=0.1, lmbda=0.1, max_iter=100):\n",
    "        self.F = F           # 这个表示隐向量的维度\n",
    "        self.P = dict()          #  用户矩阵P  大小是[users_num, F]\n",
    "        self.Q = dict()     # 物品矩阵Q  大小是[item_nums, F]\n",
    "        self.alpha = alpha   # 学习率\n",
    "        self.lmbda = lmbda    # 正则项系数\n",
    "        self.max_iter = max_iter    # 最大迭代次数\n",
    "        self.rating_data = rating_data # 评分矩阵\n",
    "        \n",
    "        # 初始化矩阵P和Q, 方法很多， 一般用随机数填充， 但随机数大小有讲究， 根据经验， 随机数需要和1/sqrt(F)成正比\n",
    "        for user, items in self.rating_data.items():\n",
    "            self.P[user] = [random.random() / math.sqrt(self.F)  for x in range(0, F)]\n",
    "            for item, rating in items.items():\n",
    "                if item not in self.Q:\n",
    "                    self.Q[item] = [random.random() / math.sqrt(self.F) for x in range(0, F)]\n",
    "    \n",
    "    # 有了矩阵之后， 就可以进行训练, 这里使用随机梯度下降的方式训练参数P和Q\n",
    "    def train(self):\n",
    "        for step in range(self.max_iter):\n",
    "            for user, items in self.rating_data.items():\n",
    "                for item, rui in items.items():\n",
    "                    rhat_ui = self.predict(user, item)   # 得到预测评分\n",
    "                    # 计算误差\n",
    "                    e_ui = rui - rhat_ui\n",
    "                    \n",
    "                    # 随机梯度下降更新梯度\n",
    "                    for k in range(0, self.F):\n",
    "                        self.P[user][k] += self.alpha * (e_ui*self.Q[item][k] - self.lmbda * self.P[user][k])\n",
    "                        self.Q[item][k] += self.alpha * (e_ui*self.P[user][k] - self.lmbda * self.Q[item][k])\n",
    "                    \n",
    "            self.alpha *= 0.1    # 每次迭代步长要逐步缩小\n",
    "    \n",
    "    # 预测user对item的评分， 这里没有使用向量的形式\n",
    "    def predict(self, user, item):\n",
    "        return sum(self.P[user][f] * self.Q[item][f] for f in range(0, self.F))   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-08-26T14:30:26.703366Z",
     "start_time": "2020-08-26T14:30:26.688365Z"
    }
   },
   "outputs": [],
   "source": [
    "class BasicSVD():\n",
    "    def __init__(self, rating_data, F=5, alpha=0.1, lmbda=0.1, max_iter=100):\n",
    "        self.F = F           # 这个表示隐向量的维度\n",
    "        self.P = dict()          #  用户矩阵P  大小是[users_num, F]\n",
    "        self.Q = dict()     # 物品矩阵Q  大小是[item_nums, F]\n",
    "        self.bu = dict()   # 用户偏差系数\n",
    "        self.bi = dict()    # 物品偏差系数\n",
    "        self.mu = 0.0        # 全局偏差系数\n",
    "        self.alpha = alpha   # 学习率\n",
    "        self.lmbda = lmbda    # 正则项系数\n",
    "        self.max_iter = max_iter    # 最大迭代次数\n",
    "        self.rating_data = rating_data # 评分矩阵\n",
    "        \n",
    "        # 初始化矩阵P和Q, 方法很多， 一般用随机数填充， 但随机数大小有讲究， 根据经验， 随机数需要和1/sqrt(F)成正比\n",
    "        cnt = 0    # 统计总的打分数， 初始化mu用\n",
    "        for user, items in self.rating_data.items():\n",
    "            self.P[user] = [random.random() / math.sqrt(self.F)  for x in range(0, F)]\n",
    "            self.bu[user] = 0\n",
    "            cnt += len(items) \n",
    "            for item, rating in items.items():\n",
    "                if item not in self.Q:\n",
    "                    self.Q[item] = [random.random() / math.sqrt(self.F) for x in range(0, F)]\n",
    "                    self.bi[item] = 0\n",
    "        self.mu /= cnt\n",
    "        \n",
    "    # 有了矩阵之后， 就可以进行训练, 这里使用随机梯度下降的方式训练参数P和Q\n",
    "    def train(self):\n",
    "        for step in range(self.max_iter):\n",
    "            for user, items in self.rating_data.items():\n",
    "                for item, rui in items.items():\n",
    "                    rhat_ui = self.predict(user, item)   # 得到预测评分\n",
    "                    # 计算误差\n",
    "                    e_ui = rui - rhat_ui\n",
    "                    \n",
    "                    self.bu[user] += self.alpha * (e_ui - self.lmbda * self.bu[user])\n",
    "                    self.bi[item] += self.alpha * (e_ui - self.lmbda * self.bi[item])\n",
    "                    # 随机梯度下降更新梯度\n",
    "                    for k in range(0, self.F):\n",
    "                        self.P[user][k] += self.alpha * (e_ui*self.Q[item][k] - self.lmbda * self.P[user][k])\n",
    "                        self.Q[item][k] += self.alpha * (e_ui*self.P[user][k] - self.lmbda * self.Q[item][k])\n",
    "                    \n",
    "            self.alpha *= 0.1    # 每次迭代步长要逐步缩小\n",
    "    \n",
    "    # 预测user对item的评分， 这里没有使用向量的形式\n",
    "    def predict(self, user, item):\n",
    "        return sum(self.P[user][f] * self.Q[item][f] for f in range(0, self.F)) + self.bu[user] + self.bi[item] + self.mu   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 34,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-08-26T14:32:23.362148Z",
     "start_time": "2020-08-26T14:32:23.345193Z"
    }
   },
   "outputs": [],
   "source": [
    "class SVDPP():\n",
    "    def __init__(self, rating_data, F=5, alpha=0.1, lmbda=0.1, max_iter=100):\n",
    "        self.F = F           # 这个表示隐向量的维度\n",
    "        self.P = dict()          #  用户矩阵P  大小是[users_num, F]\n",
    "        self.Q = dict()     # 物品矩阵Q  大小是[item_nums, F]\n",
    "        self.Y = dict()      # 用户的历史记录\n",
    "        self.bu = dict()   # 用户偏差系数\n",
    "        self.bi = dict()    # 物品偏差系数\n",
    "        self.mu = 0.0        # 全局偏差系数\n",
    "        self.alpha = alpha   # 学习率\n",
    "        self.lmbda = lmbda    # 正则项系数\n",
    "        self.max_iter = max_iter    # 最大迭代次数\n",
    "        self.rating_data = rating_data # 评分矩阵\n",
    "        \n",
    "        # 初始化矩阵P和Q, 方法很多， 一般用随机数填充， 但随机数大小有讲究， 根据经验， 随机数需要和1/sqrt(F)成正比\n",
    "        cnt = 0    # 统计总的打分数， 初始化mu用\n",
    "        for user, items in self.rating_data.items():\n",
    "            self.P[user] = [random.random() / math.sqrt(self.F)  for x in range(0, F)]\n",
    "            self.bu[user] = 0\n",
    "            cnt += len(items) \n",
    "            for item, rating in items.items():\n",
    "                self.mu += rating\n",
    "                if item not in self.Q:\n",
    "                    self.Q[item] = [random.random() / math.sqrt(self.F) for x in range(0, self.F)]\n",
    "                if item not in self.Y:\n",
    "                    self.Y[item] = [random.random() / math.sqrt(self.F) for x in range(0, self.F)]\n",
    "                self.bi[item] = 0\n",
    "        self.mu /= cnt\n",
    "        \n",
    "    # 有了矩阵之后， 就可以进行训练, 这里使用随机梯度下降的方式训练参数P和Q\n",
    "    def train(self):\n",
    "        for step in range(self.max_iter):\n",
    "            for user, items in self.rating_data.items():\n",
    "                z = [0.0 for f in range(self.F)]    # 这个z就是公式里面的y, 维度也是f\n",
    "                for item, _ in items.items():\n",
    "                    for f in range(self.F):\n",
    "                        z[f] += self.Y[item][f]           # 初始化z\n",
    "                nu = 1.0 / math.sqrt(1.0*len(items))         # 用户评分的商品的数量倒数\n",
    "                s = [0.0 for f in range(self.F)]\n",
    "                for item, rui in items.items():\n",
    "                    rhat_ui = self.predict(user, item, items)   # 得到预测评分\n",
    "                    # 计算误差\n",
    "                    e_ui = rui - rhat_ui\n",
    "                    \n",
    "                    self.bu[user] += self.alpha * (e_ui - self.lmbda * self.bu[user])\n",
    "                    self.bi[item] += self.alpha * (e_ui - self.lmbda * self.bi[item])\n",
    "                    # 随机梯度下降更新梯度\n",
    "                    for k in range(0, self.F):\n",
    "                        self.P[user][k] += self.alpha * (e_ui*self.Q[item][k] - self.lmbda * self.P[user][k])\n",
    "                        self.Q[item][k] += self.alpha * (e_ui*(self.P[user][k]+nu*z[k]) - self.lmbda * self.Q[item][k])\n",
    "                for item, _ in items.items():\n",
    "                    for f in range(self.F):\n",
    "                        self.Y[item][f] += self.alpha * (e_ui*nu*self.Y[item][f] - self.lmbda*self.Y[item][f])\n",
    "            self.alpha *= 0.1    # 每次迭代步长要逐步缩小\n",
    "    \n",
    "    # 预测user对item的评分， 这里没有使用向量的形式\n",
    "    def predict(self, user, item, ratedItems):\n",
    "        z = [0.0 for f in range(self.F)]       # 这是y\n",
    "        for ri, _ in ratedItems.items():\n",
    "            for  f in range(self.F):\n",
    "                z[f] += self.Y[ri][f]         # y的加和\n",
    "        return sum((self.P[user][f]+z[f]/math.sqrt(1.0*len(ratedItems))) * self.Q[item][f] for f in range(0, self.F)) + self.bu[user] + self.bi[item] + self.mu   "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 35,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-08-26T14:32:24.412794Z",
     "start_time": "2020-08-26T14:32:24.407808Z"
    }
   },
   "outputs": [],
   "source": [
    "# 定义数据集， 也就是那个表格， 注意这里我们采用字典存放数据， 因为实际情况中数据是非常稀疏的， 很少有情况是现在这样\n",
    "def loadData():\n",
    "    rating_data={1: {'A': 5, 'B': 3, 'C': 4, 'D': 4},\n",
    "           2: {'A': 3, 'B': 1, 'C': 2, 'D': 3, 'E': 3},\n",
    "           3: {'A': 4, 'B': 3, 'C': 4, 'D': 3, 'E': 5},\n",
    "           4: {'A': 3, 'B': 3, 'C': 1, 'D': 5, 'E': 4},\n",
    "           5: {'A': 1, 'B': 5, 'C': 5, 'D': 2, 'E': 1}\n",
    "          }\n",
    "    return rating_data"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 44,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-08-26T14:32:29.436395Z",
     "start_time": "2020-08-26T14:32:29.407472Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "E 2.0023267234588484\n"
     ]
    }
   ],
   "source": [
    "rating_data = loadData()\n",
    "rsvd = RSVD(rating_data, F=10)\n",
    "rsvd.train()\n",
    "for item in ['E']:\n",
    "    print(item, rsvd.predict(1, item))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 52,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-08-26T14:32:34.206329Z",
     "start_time": "2020-08-26T14:32:34.175416Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "E 3.252210242858994\n"
     ]
    }
   ],
   "source": [
    "rating_data = loadData()\n",
    "basicsvd = BasicSVD(rating_data, F=10)\n",
    "basicsvd.train()\n",
    "for item in ['E']:\n",
    "    print(item, basicsvd.predict(1, item))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 58,
   "metadata": {
    "ExecuteTime": {
     "end_time": "2020-08-26T14:32:39.178717Z",
     "start_time": "2020-08-26T14:32:39.111812Z"
    }
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "E 3.268324339185882\n"
     ]
    }
   ],
   "source": [
    "rating_data = loadData()\n",
    "svdpp = SVDPP(rating_data, F=10)\n",
    "svdpp.train()\n",
    "for item in ['E']:\n",
    "    print(item, svdpp.predict(1, item, rating_data[1]))"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.8"
  },
  "toc": {
   "base_numbering": 1,
   "nav_menu": {},
   "number_sections": true,
   "sideBar": true,
   "skip_h1_title": false,
   "title_cell": "Table of Contents",
   "title_sidebar": "Contents",
   "toc_cell": false,
   "toc_position": {},
   "toc_section_display": true,
   "toc_window_display": false
  },
  "varInspector": {
   "cols": {
    "lenName": 16,
    "lenType": 16,
    "lenVar": 40
   },
   "kernels_config": {
    "python": {
     "delete_cmd_postfix": "",
     "delete_cmd_prefix": "del ",
     "library": "var_list.py",
     "varRefreshCmd": "print(var_dic_list())"
    },
    "r": {
     "delete_cmd_postfix": ") ",
     "delete_cmd_prefix": "rm(",
     "library": "var_list.r",
     "varRefreshCmd": "cat(var_dic_list()) "
    }
   },
   "types_to_exclude": [
    "module",
    "function",
    "builtin_function_or_method",
    "instance",
    "_Feature"
   ],
   "window_display": false
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
